
// Copyright (C) 2007 Richard Uhler
// This file is part of Esiply.
//
// Esiply is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License, Version 2,
// as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include <assert.h>
#include <math.h>
#include <string.h>
#include "Boolean.h"
#include "Number.h"
#include "Object.h"
#include "String.h"
#include "ValueType.h"

Object* String::prototype = new Object(new String(""), Object::prototype,
        new String("String"));

String::String(const char* cstr, int length)
{
    init_with_cstr(cstr, length);
}

String::String(const char* cstr)
{
    init_with_cstr(cstr, strlen(cstr));
}

String::String(int code)
{
    m_string += code;
}

String::String(const String& rhs)
    :m_string(rhs.m_string)
{}

ValueType String::getType() const
{
    return VT_STRING;
}

void String::init_with_cstr(const char* cstr, int length)
{
    m_string = "";

    // Look for all occurences of the escape sequences: \n
    // And replace them with appropriate characters.
    for (int i = 0; i < length; i++)
    {
        if (cstr[i] == '\\')
        {
            // We should never end with a backslash. Just
            // make sure I'm not mistaken
            i++;
            assert(i < length);
            switch (cstr[i])
            {
                case 'n':
                    m_string += '\n';
                    break;

                case 't':
                    m_string += '\t';
                    break;

                case '0':
                    m_string += '\0';
                    break;

                case 'b':
                    m_string += '\b';
                    break;

                case 'v':
                    m_string += '\v';
                    break;

                case 'f':
                    m_string += '\f';
                    break;

                case 'r':
                    m_string += '\r';
                    break;

                default:
                    m_string += cstr[i];
                    break;
            }

        }
        else
        {
            m_string += cstr[i];
        }
    }
}

Boolean* String::less_than(String* lhs, String* rhs)
{
    return (lhs->m_string < rhs->m_string) ? Boolean::True : Boolean::False;
}

String* String::add(String* lhs, String* rhs)
{
    String* str = new String("");
    str->m_string = lhs->m_string + rhs->m_string;
    return str;
}

Boolean* String::equals(String* lhs, String* rhs)
{
    return lhs->m_string == rhs->m_string ? Boolean::True : Boolean::False;
}

Boolean* String::toBoolean()
{
    return m_string.empty() ? Boolean::False : Boolean::True;
}

Object* String::toObject()
{
    Object* obj = new Object(this, String::prototype, new String("String"));
    obj->put(new String("length"), new Number((double)this->length()),
            PA_DONT_ENUM | PA_DONT_DELETE | PA_READ_ONLY);
    return obj;
}

Number* String::toNumber()
{
    // TODO: this right.
    return new Number(atof(m_string.c_str()));
}


String* String::toString()
{
    return this;
}

const char* String::c_str() const
{
    return m_string.c_str();
}

std::ostream& operator<<(std::ostream& outs, const String& rhs)
{
    outs << rhs.m_string;
    return outs;
}

Number* String::charCodeAt(unsigned int pos) const
{
    // Ecma 15.5.4.5
    // Returns a number representing the code point value
    // of the character at position pos in the string. If there
    // is no character at that position, the result is NaN.
    if (pos >= m_string.size())
    {
        return new Number(NAN);
    }

    return new Number((double)m_string[pos]);
}

int String::length() const
{
    return m_string.size();
}

size_t String::hash(String* str)
{
    unsigned long h = 0;
    for (unsigned int i = 0; i < str->m_string.size(); i++)
    {
        h = 5*h + str->m_string[i];
    }
    return size_t(h);
}

bool String::equal_to(String* lhs, String* rhs)
{
    return lhs->m_string == rhs->m_string;
}

bool String::less(String* lhs, String* rhs)
{
    return lhs->m_string < rhs->m_string;
}

bool ltstring::operator()(String* lhs, String* rhs) const
{
    return String::less(lhs, rhs);
}

size_t hashstring::operator()(String* str) const
{
    return String::hash(str);
}

bool equalstring::operator()(String* lhs, String* rhs) const
{
    return String::equal_to(lhs, rhs);
}

